﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace SSG.Tools
{
    /// <summary>
    ///   Runtime tools.
    /// </summary>
    public static class RuntimeTools
    {
        /// <summary>
        ///   Get the current method.
        /// </summary>
        /// <returns></returns>
        public static MethodBase GetCurrentMethod()
        {
            return GetCallingMethod();
        }

        /// <summary>
        ///   Get the calling method, this will not necessarily work in productive code.
        ///   Relatively slow because of using the <see cref = "StackTrace" />.
        /// </summary>
        /// <param name = "offset">Method offset in stack trace frame (1..current method, 2..calling method)</param>
        /// <returns></returns>
        public static MethodBase GetCallingMethod(int offset = 2)
        {
            //  EXTERNAL: http://www.csharp-examples.net/reflection-calling-method-name/
            //  EXTERNAL: http://stackoverflow.com/questions/171970/how-can-i-find-the-method-that-called-the-current-method"

            // get call stack
            StackTrace stackTrace = new StackTrace();

            // get calling method name
            // default "2" because I do not want to get the caller of this method, but the one before
            if (stackTrace.GetFrame(offset) == null) return null;
            return stackTrace.GetFrame(offset).GetMethod();
        }

        /// <summary>
        ///   Method name of caller.
        /// </summary>
        /// <returns></returns>
        public static string GetCallingMethodName()
        {
            MethodBase mb = RuntimeTools.GetCallingMethod(3);
            return (mb != null) ? mb.Name : null;
        }

        /// <summary>
        ///   Method name (current method).
        /// </summary>
        /// <returns></returns>
        public static string GetCurrentMethodName()
        {
            MethodBase mb = RuntimeTools.GetCallingMethod();
            return (mb != null) ? mb.Name : null;
        }

        /// <summary>
        ///   List of <paramref name = "depth" /> calling methods from <paramref name = "offset" />.
        /// </summary>
        /// <param name = "offset"></param>
        /// <param name = "depth"></param>
        /// <param name = "includeClassname"></param>
        /// <returns></returns>
        public static string GetMethodNames(int offset, int depth, bool includeClassname = false)
        {
            // get call stack and init
            StackTrace stackTrace = new StackTrace();
            StringBuilder sb = new StringBuilder();

            // get calling method name
            for (int i = offset; i < offset + depth; i++) {
                StackFrame f = stackTrace.GetFrame(i);
                if (f == null) break;
                MethodBase method = f.GetMethod();
                if (method == null || method.DeclaringType == null) continue;
                string className = includeClassname ? method.DeclaringType.FullName : null;
                string sep = method.Name.StartsWith(".") ? "" : ".";
                string m = className != null ? className + sep + method.Name : method.Name;
                sb.AppendIfNotNullOrEmpty(m, ", ");
            }
            return sb.ToString();
        }

        /// <summary>
        ///   Does list of <paramref name = "depth" /> calling methods from <paramref name = "offset" />
        ///   contain <paramref name="classname"/>?
        ///   <br/>
        ///   This is a simple case insensitive string search.
        /// </summary>
        /// <param name = "classname"></param>
        /// <param name = "offset"></param>
        /// <param name = "depth"></param>
        /// <returns></returns>
        public static bool CallStackContainsClass(string classname, int offset, int depth)
        {
            if (String.IsNullOrEmpty(classname)) return false;

            // get call stack and init
            StackTrace stackTrace = new StackTrace();
            string cn = classname.ToLower();

            // get calling method name
            for (int i = offset; i < offset + depth; i++) {
                StackFrame f = stackTrace.GetFrame(i);
                if (f == null) break;
                MethodBase method = f.GetMethod();
                if (method == null || method.DeclaringType == null) continue;
                string className = method.DeclaringType.FullName;
                if (className == null) continue;
                if (className.ToLower().Contains(cn)) return true;
            }
            return false;
        }

        /// <summary>
        ///   List of <paramref name = "depth" /> calling methods from <paramref name = "offset" />.
        /// </summary>
        /// <param name = "offset"></param>
        /// <param name = "depth"></param>
        /// <returns></returns>
        public static string GetMethodNamesReverse(int offset, int depth)
        {
            // get call stack and init
            StackTrace stackTrace = new StackTrace();
            StringBuilder sb = new StringBuilder();

            // get calling method name
            for (int i = offset + depth - 1; i >= offset; i--) {
                StackFrame f = stackTrace.GetFrame(i);
                if (f == null) continue;
                string m = f.GetMethod().Name;
                sb.AppendIfNotNullOrEmpty(m, ", ");
            }
            return sb.ToString();
        }

        /// <summary>
        ///   Information about loaded <c>ProcessModule</c> with the given <paramref name = "moduleName" />.
        /// </summary>
        /// <param name = "name"></param>
        /// <param name = "moduleName"></param>
        /// <param name = "path"></param>
        /// <param name = "version"></param>
        /// <returns></returns>
        /// <seealso cref = "FileTools.GetAssemblyInformation" />
        public static ProcessModule GetLoadedModule(this string name, out string moduleName, out string path, out string version)
        {
            moduleName = path = version = null;
            if (String.IsNullOrEmpty(name)) return null;
            ProcessModule m = GetLoadedModule(name);

            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (m == null || m.FileVersionInfo == null) return null;
            // ReSharper restore ConditionIsAlwaysTrueOrFalse

            // get info
            FileVersionInfo fvi = m.FileVersionInfo;
            moduleName = StringTools.FirstNotEmptyValue(fvi.OriginalFilename); // fvi.FileName contains path
            version = StringTools.FirstNotEmptyValue(fvi.ProductVersion, fvi.FileVersion);
            path = Path.GetDirectoryName(fvi.FileName);
            return m;
        }

        /// <summary>
        ///   Get <c>ProcessModule</c> with given <paramref name = "name" />.
        /// </summary>
        /// <param name = "name"></param>
        /// <returns></returns>
        public static ProcessModule GetLoadedModule(this string name)
        {
            if (String.IsNullOrEmpty(name)) return null;
            ProcessModuleCollection modules = Process.GetCurrentProcess().Modules;
            return modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.NullSafeEqualsIgnoreCase(name));
        }
    }
}